use std::{
    fs::{self, Metadata, OpenOptions},
    io::{self, Write},
    net::AddrParseError,
    ops::Div,
    path::Path,
    time::SystemTime,
};
mod pyo;

use chrono::{DateTime, Local};
#[cfg(target_os = "linux")]
use pyo::test_docker_invoke;

#[cfg(target_os = "windows")]
use pyo::test_docker_invoke_windows;

use pyo::{Learner, ParamsBuilder, Predict};
use sandbox::{
    file_response_list::SameCsv,
    get_file_list_server::{GetFileList, GetFileListServer},
    invoke_server::{Invoke, InvokeServer},
    upload_server::{Upload, UploadServer},
    FileRequest, FileResponse, FileResponseList, PythonRequest, PythonResponse, UploadRequest,
    UploadResponse,
};
use tonic::{async_trait, transport::Server, Request, Response, Status};

pub trait PathExt {
    fn get_csvname(&self) -> String;
    fn get_filename(&self) -> String;
    fn get_extensions(&self) -> String;
}
impl PathExt for Path {
    #[inline]
    fn get_csvname(&self) -> String {
        let filename = self.get_filename();
        filename
            .split_at(filename.find(".csv").unwrap() + 4)
            .0
            .to_string()
    }
    #[inline]
    fn get_extensions(&self) -> String {
        self.extension().unwrap().to_str().unwrap().to_string()
    }
    #[inline]
    fn get_filename(&self) -> String {
        self.file_name().unwrap().to_str().unwrap().to_string()
    }
}

pub mod sandbox {
    tonic::include_proto!("sandbox");
}

#[derive(Debug, Default)]
pub struct UploadService {}

#[tonic::async_trait]
impl Upload for UploadService {
    async fn upload_file(
        &self,
        request: Request<UploadRequest>,
    ) -> Result<Response<UploadResponse>, Status> {
        let params = request.into_inner();
        #[cfg(target_os = "linux")]
        let path = format!("/app/model/{}.{}", params.file_name, params.file_extensions);
        #[cfg(target_os = "windows")]
        let path = format!("/app/model/{}.{}", params.file_name, params.file_extensions);
        let mut fs = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(path)
            .unwrap();
        fs.write_all(&params.data)
            .map_err(|_| Status::aborted("向文件写入数据失败"))?;
        println!("request: {:#?}", params);
        let response = UploadResponse { successful: true };
        Ok(Response::new(response))
    }
}
#[derive(Debug, Default)]
struct InvokeService {}
#[tonic::async_trait]
impl Invoke for InvokeService {
    async fn invoke(
        &self,
        request: Request<PythonRequest>,
    ) -> Result<Response<PythonResponse>, Status> {
        println!("{:#?}", request);
        #[cfg(target_os = "linux")]
        test_docker_invoke().map_err(|e| Status::aborted(e.to_string()))?;

        #[cfg(target_os = "windows")]
        test_docker_invoke_windows().map_err(|e| Status::aborted(e.to_string()))?;
        Ok(Response::new(PythonResponse {
            successful: true,
            path: "".into(),
        }))
    }

    async fn seir_predict(
        &self,
        request: Request<PythonRequest>,
    ) -> Result<Response<PythonResponse>, Status> {
        let request = request.into_inner();
        let filename = Path::new(&request.filename);
        let file_extensions = filename
            .extension()
            .ok_or_else(|| Status::aborted("文件没有后缀名"))?
            .to_str()
            .ok_or_else(|| Status::aborted("未知错误"))?;
        let file_name = filename
            .file_name()
            .ok_or_else(|| Status::aborted("文件名错误"))?
            .to_str()
            .ok_or_else(|| Status::aborted("未知错误"))?;
        let params = ParamsBuilder::builder()
            .with_username(request.username.clone())
            .with_filename(file_name.to_string())
            .with_filetype(file_extensions.to_string())
            .build();
        let result = Learner::new().pyo3_predict(params).map_err(|e| {
            log::error!("{}", e.to_string());
            Status::aborted(e.to_string())
        })?;
        println!(
            "用户:{} 调用SEIR模型完成: 文件为{}",
            request.username, request.filename
        );
        Ok(Response::new(PythonResponse {
            successful: true,
            path: result,
        }))
    }
    async fn sir_predict(
        &self,
        request: Request<PythonRequest>,
    ) -> Result<Response<PythonResponse>, Status> {
        Ok(Response::new(PythonResponse {
            successful: false,
            path: "".into(),
        }))
    }
}
#[derive(Debug, Default)]
struct FileSevice;
impl FileResponse {
    pub fn new(
        filename: String,
        file_extensions: String,
        filesize: String,
        modified_time: String,
        csv_name: String,
    ) -> Self {
        Self {
            filename,
            file_extensions,
            filesize,
            modified_time,
            csv_name,
        }
    }
    pub fn size(mut data: f64) -> String {
        let mut filesize = String::new();
        let mut count = 0;
        loop {
            if data.div(1024.0) < 1.0 {
                match count {
                    0 => {
                        filesize = format!("{}B", data);
                    }
                    1 => {
                        filesize = format!("{:.2}KB", data);
                    }
                    2 => {
                        filesize = format!("{:.2}MB", data);
                    }
                    _ => {}
                };
                break;
            };
            data = data / 1024.0;
            count = count + 1;
        }
        filesize
    }
}
pub trait SystemTimeExt {
    fn format(self) -> String;
}
impl SystemTimeExt for SystemTime {
    fn format(self) -> String {
        let datetime: DateTime<Local> = self.into();
        datetime.format("%Y-%m-%d %H:%M:%S").to_string()
    }
}
impl FileSevice {
    #[cfg(target_os = "linux")]
    pub fn files(username: &str) -> io::Result<Vec<String>> {
        let mut array = Vec::new();
        let dir_string = format!("/app/result/{}", username);
        let dir = Path::new(&dir_string);
        for entry in fs::read_dir(dir)? {
            let entry = entry?;

            let path = entry.path();

            if path.is_file() {
                if let Some(path_str) = path.to_str() {
                    array.push(path_str.into());
                }
            }
        }
        Ok(array)
    }
    #[cfg(target_os = "windows")]
    pub fn files(username: &str) -> io::Result<Vec<Vec<FileResponse>>> {
        use std::{collections::HashMap, fs::OpenOptions};

        let dir_string = format!("D:/Rust_Project/tonic_for_ffi/{}", username);
        let dir = Path::new(&dir_string);
        let mut map: HashMap<String, Vec<FileResponse>> = HashMap::new();
        for entry in fs::read_dir(dir)? {
            let entry = entry?;

            let path = entry.path();

            if path.is_file() {
                let filename = path.get_filename();
                let csv_name = path.get_csvname();
                let file_extensions = path.get_extensions();
                let file = FileSevice::read_file(path);
                let data = file.len();
                let modified_time = file.modified()?;
                let response = FileResponse::default()
                    .csv_name(csv_name.to_owned())
                    .filename(filename)
                    .file_extensions(file_extensions)
                    .modified_time(modified_time.format())
                    .filesize(FileResponse::size(data as f64));
                let file_array = map.entry(csv_name).or_insert(vec![]);
                file_array.push(response);
            }
        }
        Ok(map.into_values().collect::<Vec<Vec<FileResponse>>>())
    }
    #[inline]

    fn read_file(path: impl AsRef<Path>) -> Metadata {
        OpenOptions::new()
            .read(true)
            .open(path)
            .unwrap()
            .metadata()
            .unwrap()
    }
}
impl FileResponseList {
    #[inline]
    pub fn new<F>(closure: F) -> Self
    where
        F: FnOnce() -> Vec<SameCsv>,
    {
        let data = closure();
        Self { diff_files: data }
    }
}
impl SameCsv {
    pub fn new() -> Self {
        Self { files: vec![] }
    }
}
impl FileResponse {
    #[inline]
    fn filename(mut self, filename: String) -> Self {
        self.filename = filename;
        self
    }
    #[inline]
    fn csv_name(mut self, csv_name: String) -> Self {
        self.csv_name = csv_name;
        self
    }
    #[inline]
    fn file_extensions(mut self, file_extensions: String) -> Self {
        self.file_extensions = file_extensions;
        self
    }
    #[inline]
    fn modified_time(mut self, modified_time: String) -> Self {
        self.modified_time = modified_time;
        self
    }
    #[inline]
    fn filesize(mut self, filesize: String) -> Self {
        self.filesize = filesize;
        self
    }
}
#[async_trait]
impl GetFileList for FileSevice {
    async fn get_file_list(
        &self,
        request: Request<FileRequest>,
    ) -> Result<Response<FileResponseList>, Status> {
        let request = request.into_inner();
        let array = FileSevice::files(&request.username).map_err(|e| {
            log::error!("{}", e.to_string());
            Status::aborted(e.to_string())
        })?;
        Ok(Response::new(FileResponseList::new(move || {
            array
                .into_iter()
                .map(|items| {
                    let mut temp = SameCsv::new();
                    temp.files.extend(items);
                    temp
                })
                .collect()
        })))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();
    println!("{}", "RPC服务正在运行!");
    let addr = "[::]:50050".parse().map_err(|e: AddrParseError| {
        println!("{}", e.to_string());
        "Error"
    })?;
    let sandbox = UploadService::default();
    let python = InvokeService::default();
    let files = FileSevice::default();
    Server::builder()
        .add_service(UploadServer::new(sandbox))
        .add_service(InvokeServer::new(python))
        .add_service(GetFileListServer::new(files))
        .serve(addr)
        .await?;
    println!("{}", "RPC服务退出!");
    Ok(())
}
